package top.milkbox.common.utils;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ObjectUtil;
import top.milkbox.common.utils.base.CommonNode;
import top.milkbox.common.utils.base.CycleRefException;

import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.Stream;

/**
 * 集合工具类
 *
 * @author 郭泳辰
 */
public class CommonCollUtil {

    /**
     * 获取给定集合中重复的元素<br />
     * <pre>
     * // 使用举例：
     * List&lt;ImportCategoryData&gt; cachedDataList = new ArrayList<>();
     * // cachedDataList............
     * List&lt;String&gt; duplicateList =
     *          CommonCollUtil.getDuplicateElements(cachedDataList, ImportCategoryData::getCode);
     * if (ObjectUtil.isNotEmpty(duplicateList)) {
     *      // 某某某重复
     *      duplicateList.forEach(item -> log.info("某某某【" + item + "】重复"));
     * }
     * </pre>
     *
     * @param objectList 指定集合
     * @param function   指定字段
     * @param <T>        集合中对象的类型
     * @param <R>        指定的字段类型
     * @return 返回集合中重复的所有元素
     */
    public static <T, R> List<R> getDuplicateElements(Collection<T> objectList, Function<T, R> function) {
        return getDuplicateElements(objectList.stream().map(function));
    }

    /**
     * 获取给定的简单集合中重复的元素<br />
     * 仅支持简单类型的集合，复杂类型集合请使用重载方法：{@link #getDuplicateElements(Collection, Function)}
     *
     * @param simpleList 指定集合
     * @param <T>        集合中对象的类型，必须是简单类型对象
     * @return 返回重复的所有元素
     */
    public static <T> List<T> getDuplicateElements(Collection<T> simpleList) {
        return getDuplicateElements(simpleList.stream());
    }

    /**
     * 获取给定的流中重复的元素<br />
     * 依靠分组操作求重复元素
     *
     * @param stream 指定的流对象（内部必须是简单类型）
     * @param <T>    流中元素的类型，必须是简单类型
     * @return 返回重复的元素
     */
    public static <T> List<T> getDuplicateElements(Stream<T> stream) {
        return stream
                // 去空
                .filter(ObjectUtil::isNotNull)
                // 分组
                .collect(Collectors.groupingBy(Function.identity(), Collectors.counting()))
                // 获取键值对集合
                .entrySet().stream()
                // 值大于1的键就是重复的元素
                .filter(entry -> entry.getValue() > 1)
                // 获取键
                .map(Map.Entry::getKey)
                // 转为键集合
                .collect(Collectors.toList());
    }


    /**
     * 线性集合转为森林<br />
     * <pre>
     * 节点类型必须实现{@link CommonNode}接口
     *
     * 满足一下任意条件表示是一个顶级节点：
     * 1. 当前节点的parentId为空
     * 2. 当前节点的parentId为空或者等于0
     * 3. 当前节点的有parentId但是未在节点集合中找到其上级
     * </pre>
     *
     * @param nodes 节点集合
     * @param <S>   节点id类型
     * @param <T>   节点类型
     * @return 返回森林
     * @throws CycleRefException 构建过程中出现循环引用异常
     */
    public static <S, T extends CommonNode<S>> List<T> toForest(List<T> nodes) throws CycleRefException {
        Map<S, T> nodeMap = nodes.stream().collect(Collectors.toMap(CommonNode::getId, node -> node));
        List<T> rootForest = new ArrayList<>();

        for (T node : nodes) {
            if (ObjectUtil.isEmpty(node.getParentId()) || CommonUtil.isZero(node.getParentId())) {
                rootForest.add(node);
            } else {
                T parentNode = nodeMap.get(node.getParentId());
                if (parentNode != null) {
                    LinkedList<S> path = findFromForest(node.getParentId(), Collections.singletonList(node));
                    if (ObjectUtil.isNotEmpty(path)) {
                        throw new CycleRefException("循环引用：" + CollUtil.join(path, " <- "));
                    }
                    if (parentNode.getChildrenList() == null) {
                        parentNode.initChildrenList();
                    }
                    parentNode.getChildrenList().add(node);
                } else {
                    rootForest.add(node);
                }
            }
        }

        return rootForest;
    }

    /**
     * 递归查找指定节点在森林中的路径
     *
     * @param id     节点id
     * @param forest 森林
     * @param path   节点所在的路径
     * @param <S>    节点id类型
     * @param <T>    节点类型
     * @return 递归过程中使用，true表示找到节点并立即结束整个递归
     */
    private static <S, T extends CommonNode<S>> boolean findFromForestDFS(S id, List<T> forest, LinkedList<S> path) {
        if (ObjectUtil.isEmpty(forest)) {
            return false;
        }
        for (T node : forest) {
            path.add(node.getId());

            // 找到目标节点 或者 下一层递归告诉我需要立即结束递归，则返回true（返回true表示立即结束整个递归）
            if (id.equals(node.getId()) || findFromForestDFS(id, node.getChildrenList(), path)) {
                return true;
            }

            path.removeLast();
        }
        return false;
    }

    /**
     * 找到指定节点在森林中的路径
     *
     * @param id     节点id
     * @param forest 森林
     * @param <S>    节点id类型
     * @param <T>    节点类型
     * @return 如果找到，则返回路径，否则返回空集合
     */
    public static <S, T extends CommonNode<S>> LinkedList<S> findFromForest(S id, List<T> forest) {
        LinkedList<S> path = new LinkedList<>();
        findFromForestDFS(id, forest, path);
        return path;
    }

    /**
     * 判断指定节点是否在森林中
     *
     * @param id     节点id
     * @param forest 森林
     * @param <S>    节点id类型
     * @param <T>    节点类型
     * @return 如果在，则返回true，否则返回false
     */
    public static <S, T extends CommonNode<S>> boolean isInForest(S id, List<T> forest) {
        return ObjectUtil.isNotEmpty(findFromForest(id, forest));
    }

}
